#include "../../NahidaProject.UnitTest/Sources/NahidaUnitTest.h"

#include "../Sources/BloomFilter.h"
#include "../Sources/Date.h"
#include "../Sources/DateTime.h"
#include "../Sources/FileHelper.h"
#include "../Sources/Logger.h"
#include "../Sources/NahidaProject.h"
#include "../Sources/SnowFlake.h"
#include "../Sources/SortModule.h"
#include "../Sources/StringValidator.h"
#include "../Sources/Time.h"
#include "../Sources/TimerGroup.h"

Test(BloomFilterTest) {
	NahidaProject::BloomFilter<100> bloomFilter;

	bloomFilter.Set("Object A");
	bloomFilter.Set("Object B");
	bloomFilter.Set("Object C");

	AssertEqual(bloomFilter.Check("Object A"), 1);
	AssertEqual(bloomFilter.Check("Object B"), 1);
	AssertEqual(bloomFilter.Check("Object C"), 1);
	AssertEqual(bloomFilter.Check("Object D"), 0);

	AssertEqual(bloomFilter.EstimateElementCount(), 3);

	std::cout << "Used bits: " << bloomFilter.GetUsedBits() << std::endl;
	std::cout << "Load factor: " << bloomFilter.LoadFactor() << std::endl;
	std::cout << "False positive rate: " << bloomFilter.EstimateFalsePositiveRate(3) << std::endl;
	bloomFilter.Clear();
	AssertEqual(bloomFilter.GetUsedBits(), 0);
}

Test(DateTest) {
	NahidaProject::Date date1(2024, 2, 29);
	NahidaProject::Date date2(2024, 3, 15);

	std::cout << "Date1: " << date1.ToString() << " (" << date1.GetWeekdayString() << ")" << std::endl;
	std::cout << "Date2: " << date2.ToString() << " (" << date2.GetWeekdayString() << ")" << std::endl;

	std::cout << "\nComparison:" << std::endl;
	std::cout << "date1 < date2: " << (date1 < date2) << std::endl;
	std::cout << "date1 == date2: " << (date1 == date2) << std::endl;

	std::cout << "\nArithmetic operations:" << std::endl;
	NahidaProject::Date date3 = date1 + 10;
	std::cout << "date1 + 10 days: " << date3.ToString() << std::endl;

	int diff = date2 - date1;
	std::cout << "Difference between date2 and date1: " << diff << " days" << std::endl;

	std::cout << "\nDate modification:" << std::endl;
	NahidaProject::Date date4(date1);
	date4.AddMonths(1);
	std::cout << "date1 + 1 month: " << date4.ToString() << std::endl;

	date4.AddYears(1);
	std::cout << "date4 + 1 year: " << date4.ToString() << std::endl;

	std::cout << "\nFormatted output:" << std::endl;
	std::cout << "Custom format: " << date1.ToString("DD/MM/YYYY") << std::endl;
	std::cout << "Default format: " << date1.ToString() << std::endl;

	std::cout << "Special dates:" << std::endl;
	std::cout << "Start of month: " << date2.StartOfMonth().ToString() << std::endl;
	std::cout << "End of month: " << date2.EndOfMonth().ToString() << std::endl;
	std::cout << "Is end of month: " << date2.IsEndOfMonth() << std::endl;

	std::cout << "Increment/decrement:" << std::endl;
	NahidaProject::Date date5(2024, 1, 1);
	std::cout << "Original date: " << date5.ToString() << std::endl;
	std::cout << "Pre-increment: " << (++date5).ToString() << std::endl;
	std::cout << "Post-increment: " << date5++.ToString() << std::endl;
	std::cout << "After post-increment: " << date5.ToString() << std::endl;
}

Test(DateTimeTest) {
	try {
		NahidaProject::DateTime dt1(2024, 1, 15, 14, 30, 45);
		std::cout << "dt1: " << dt1.ToString() << std::endl;

		NahidaProject::DateTime now = NahidaProject::DateTime::Now();
		std::cout << "Now: " << now.ToString() << std::endl;

		NahidaProject::DateTime dt2 = NahidaProject::DateTime::Parse("2024-03-20 10:15:30");
		std::cout << "Parsed: " << dt2.ToString() << std::endl;

		std::cout << "Formatted: " << dt1.ToString("%Y/%m/%d %H:%M") << std::endl;
		std::cout << "Date only: " << dt1.ToString("%Y-%m-%d") << std::endl;

		NahidaProject::DateTime dt3 = dt1 + 3600;
		std::cout << "dt1 + 1 hour: " << dt3.ToString() << std::endl;

		NahidaProject::DateTime dt4 = dt1 - 1800;
		std::cout << "dt1 - 30 minutes: " << dt4.ToString() << std::endl;

		double diff = dt2 - dt1;
		std::cout << "Difference: " << diff << " seconds" << std::endl;

		std::cout << "dt1 < dt2: " << (dt1 < dt2 ? "true" : "false") << std::endl;
		std::cout << "dt1 == dt2: " << (dt1 == dt2 ? "true" : "false") << std::endl;

		std::cout << "Weekday of dt1: " << dt1.GetWeekday() << std::endl;
		std::cout << "Day of year of dt1: " << dt1.GetDayOfYear() << std::endl;
		std::cout << "Is leap year: " << (dt1.IsLeapYear() ? "true" : "false") << std::endl;

		dt1.SetHour(16);
		dt1.SetMinute(45);
		std::cout << "Modified dt1: " << dt1.ToString() << std::endl;

	}
	catch (const std::exception& e) {
		std::cerr << "Error: " << e.what() << std::endl;
	}
}

Test(FileHelperTest) {
	try {
		NahidaProject::FileHelper handler("test.txt");

		std::cout << "Creating file..." << std::endl;
		handler.CreateFile();

		std::cout << "Writing text..." << std::endl;
		handler.WriteText("Hello, World!\nThis is a test file.\n");

		std::cout << "Appending text..." << std::endl;
		handler.AppendText("This line was appended.\n");

		std::cout << "Reading text:" << std::endl;
		std::string content = handler.ReadWholeText();
		std::cout << content << std::endl;

		std::cout << "Reading lines:" << std::endl;
		std::vector<std::string> lines = handler.ReadLines();
		for (size_t i = 0; i < lines.size(); ++i) {
			std::cout << "Line " << (i + 1) << ": " << lines[i] << std::endl;
		}

		std::cout << "File size: " << handler.GetFileSize() << " bytes" << std::endl;
		std::cout << "Is empty: " << (handler.IsEmpty() ? "Yes" : "No") << std::endl;

		std::cout << "File exists: " << (handler.Exists() ? "Yes" : "No") << std::endl;

		std::cout << "\nCopying file..." << std::endl;
		NahidaProject::FileHelper::CopyFile("test.txt", "test_copy.txt");
		std::cout << "File copied successfully." << std::endl;

		std::cout << "Moving file..." << std::endl;
		NahidaProject::FileHelper::MoveFile("test_copy.txt", "renamed_test.txt");
		std::cout << "File moved/renamed successfully." << std::endl;

		std::cout << "Clearing original file..." << std::endl;
		handler.ClearFile();
		std::cout << "Original file cleared. Is empty: " << (handler.IsEmpty() ? "Yes" : "No") << std::endl;

		std::cout << "Deleting files..." << std::endl;
		NahidaProject::FileHelper::DeleteFile("test.txt");
		NahidaProject::FileHelper::DeleteFile("renamed_test.txt");
		std::cout << "Files deleted successfully." << std::endl;

	}
	catch (const std::exception& e) {
		std::cerr << "Error: " << e.what() << std::endl;
	}

	try {
		NahidaProject::FileHelper handler("binary_test.dat");
		std::vector<char> data = { 'H', 'e', 'l', 'l', 'o', '\0', 'W', 'o', 'r', 'l', 'd' };

		std::cout << "Writing binary data..." << std::endl;
		handler.WriteBinary(data);

		std::cout << "Reading binary data..." << std::endl;
		std::vector<char> readData = handler.ReadBinary();

		std::cout << "Read data: ";
		for (char c : readData) {
			if (c == '\0') {
				std::cout << "\\0";
			}
			else {
				std::cout << c;
			}
		}
		std::cout << std::endl;

		NahidaProject::FileHelper::DeleteFile("binary_test.dat");
	}
	catch (const std::exception& e) {
		std::cerr << "Error: " << e.what() << std::endl;
	}
}

Test(NahidaProjectTest) {
	std::cout << NahidaProject::NahidaProject::Version()<< std::endl;
	NahidaProject::NahidaProject::HelloWorld();
}

Test(LoggerTest) {
	NahidaProject::Logger::GetInstance()->Open("TestRead.log");
	NahidaProject::Logger::GetInstance()->SetMax(1024);

	Debug("This is a debug imformation");
	Information("This is a imformation imformation");
	Warning("This is a %s imformation", "warning");
	Error("This is a error imformation");
	// FATAL("Hello NahidaProject"); Will be stop and return -1
}

Test(SnowFlakeTest) {
	NahidaProject::SnowFlake snowFlake(1);

	for (int i = 0; i < 10; ++i) {
		std::cout << snowFlake.NextId() << std::endl;
	}
}

Test(SortModuleTest) {
	std::vector<int> a = { 5, 6, 7, 2, 1, 4, 3 };
	std::vector<int> b = { 1, 2, 3, 4, 5, 6, 7 };

	AssertTrue(NahidaProject::SortModule::BubbleSort(a) == b);
    AssertTrue(NahidaProject::SortModule::InsertSort(a) == b);
	AssertTrue(NahidaProject::SortModule::SelectSort(a) == b);
}

Test(StringValidatorTest) {
	NahidaProject::StringValidator validator;

	auto result = validator.Validate("test@example.com", NahidaProject::StringValidator::ValidationType::EMAIL);
	std::cout << "Email validation: " << (result.isValid ? "Valid" : "Invalid") << std::endl;

	result = validator.ValidateLength("Hello", 3, 10);
	std::cout << "Length validation: " << (result.isValid ? "Valid" : "Invalid") << std::endl;

	result = validator.ValidateCustom("12321", [](const std::string& text) {
		return text == std::string(text.rbegin(), text.rend());
	}, "Not a palindrome");
	std::cout << "Palindrome validation: " << (result.isValid ? "Valid" : "Invalid") << std::endl;
}

Test(TimeTest) {
	NahidaProject::Time t1(14, 30, 25);
	std::cout << "Time 1: " << t1.ToString() << std::endl;
	std::cout << "12-hour format: " << t1.To12HourFormat() << std::endl;

	NahidaProject::Time t2(9, 15, 30);
	std::cout << "Time 2: " << t2.ToString() << std::endl;

	NahidaProject::Time t3 = t1 + t2;
	std::cout << "t1 + t2 = " << t3.ToString() << std::endl;

	NahidaProject::Time t4 = t1 - t2;
	std::cout << "t1 - t2 = " << t4.ToString() << std::endl;

	std::cout << "t1 > t2: " << (t1 > t2 ? "true" : "false") << std::endl;
	std::cout << "t1 == t2: " << (t1 == t2 ? "true" : "false") << std::endl;

	std::cout << "Original t1: " << t1.ToString() << std::endl;
	std::cout << "++t1: " << t1.ToString() << std::endl;
	std::cout << "t1++: " << t1++.ToString() << std::endl;
	std::cout << "After increment: " << t1.ToString() << std::endl;

	t1.AddHours(2);
	t1.AddMinutes(30);
	t1.AddSeconds(15);
	std::cout << "After adding time: " << t1.ToString() << std::endl;

	NahidaProject::Time t5 = NahidaProject::Time::Parse("23:59:59");
	std::cout << "Parsed time: " << t5.ToString() << std::endl;

	try {
		NahidaProject::Time invalid(-1, 30, 30);
	}
	catch (const std::exception& e) {
		std::cout << e.what() << std::endl;
	}
}

Test(TimerGroupTest) {
	NahidaProject::HighPrecisionTimer timer;
	timer.Start();

	for (int i = 0; i < 1000000; ++i) {
		volatile double x = std::sin(i);
	}

	timer.Stop();
	std::cout << timer.ElapsedMilliSeconds() << "ms" << std::endl;
	std::cout << timer.ElapsedMicroSeconds()<< "us" << std::endl;

	NahidaProject::CallbackTimer cb_timer(std::chrono::milliseconds(500), []() {
		std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count()<< " ms" << std::endl;
	});

	cb_timer.Start();
	std::this_thread::sleep_for(std::chrono::seconds(3));
	cb_timer.Stop();

	NahidaProject::PerformanceTimer perf_timer;

	for (int i = 0; i < 10; ++i) {
		perf_timer.Begin();

		volatile double sum = 0;
		for (int j = 0; j < 100000; ++j) {
			sum += std::sqrt(j);
		}

		perf_timer.End();
	}

	perf_timer.PrintStatistics();

	NahidaProject::HighPrecisionTimer delay_timer;

	delay_timer.Start();
	NahidaProject::PrecisionDelay::DelayMilliSeconds(100);
	delay_timer.Stop();

	std::cout << delay_timer.ElapsedMilliSeconds() << std::endl;
}

int main() {
	RunAllTestCase();
	return 0;
}